/* eslint-disable func-names */
/* eslint-disable no-extend-native */

declare global {
	interface Array<T> {
		count(): number; // fillter null
		countIf(predicate: (value: T) => boolean): number;
		insert(index: number, o: T): void;
		remove(o: T): boolean;
		indexIf(predicate: (value: T) => boolean): number;
		indexIfa(predicate: (value: T) => boolean): T;
		removeIf(predicate: (value: T) => boolean): number;
		removeIfa(predicate: (value: T) => boolean): T;
		removeIfrt(predicate: (value: T) => boolean): Array<T>;
		shuffle(): void;
		random(): T;
		randoms(value: number): T[];
		randomConcat(value: number, ...a: Array<T>[]): T[];
		randomByQuan(quan: (value: T) => number): T;
		max(quan: (value: T) => number): number;
		min(quan: (value: T) => number): number;
	}
}

function randomQuan(quans: number[]): number {
	const sum = quans.reduce((prev, cur) => prev + cur);
	let rand = Math.randomInt(0, sum);
	let index = 0;
	for (const quan of quans) {
		if (rand <= quan) {
			return index;
		}
		rand -= quan;
		index++;
	}
	return 0;
}

function getArrayValue(index: number, ...a: any[][]) {
	let s = 0;
	for (const e of a) {
		const n = s + e.length;
		if (index < n) {
			return e[index - s];
		}
		s = n;
	}
}

export function extendArray(): void {
	// console.log("extend array");
	Array.prototype.remove = function <T>(elem: T): boolean {
		const index = this.indexOf(elem, 0);
		if (index > -1) {
			this.splice(index, 1);
			return true;
		}
		return false;
	};

	Array.prototype.insert = function <T>(index: number, elem: T): void {
		this.splice(index, 0, elem);
	};

	Array.prototype.random = function <T>(): T {
		const i = Math.randomInt(0, this.length);
		return this[i];
	};

	Array.prototype.randoms = function <T>(value: number): T[] {
		let s = this.length;
		if (value >= s) {
			return this;
		}
		const rt = [];
		const temp = {};
		while (value--) {
			const i = Math.randomInt(0, s);
			if (temp.hasOwnProperty(i)) {
				rt.push(temp[i]);
			} else {
				rt.push(this[i]);
			}
			temp[i] = this[s - 1];
			s--;
		}
		return rt;
	};

	Array.prototype.randomConcat = function <T>(value: number, ...a: Array<T>[]): T[] {
		let s = this.length;
		for (const e of a) {
			s += e.length;
		}
		if (s <= value) {
			return this.concat(...a);
		}
		const rt = [];
		const temp = {};
		while (value--) {
			const i = Math.randomInt(0, s);
			let v: T;
			if (temp.hasOwnProperty(i)) {
				v = temp[i];
			} else {
				v = getArrayValue(i, this, ...a);
			}
			rt.push(v);
			temp[i] = getArrayValue(s - 1, this, ...a);
			s--;
		}
		return rt;
	};

	Array.prototype.randomByQuan = function <T>(quan: (value: T) => number): T {
		if (this.length === 0) {
			return null;
		}
		const quans = this.map(quan);
		const index = randomQuan(quans);
		return this[index];
	};

	Array.prototype.max = function <T>(quan: (value: T) => number): number {
		if (this.length === 0) {
			return 0;
		}
		const quans = this.map(quan);
		return quans.reduce((p: number, v: number) => (p > v ? p : v));
	};
	Array.prototype.min = function <T>(quan: (value: T) => number): number {
		if (this.length === 0) {
			return 0;
		}
		const quans = this.map(quan);
		return quans.reduce((p: number, v: number) => (p < v ? p : v));
	};

	Array.prototype.indexIf = function <T>(predicate: (value: T) => boolean): number {
		let i = 0;
		while (i < this.length) {
			if (this[i] != null && predicate(this[i])) {
				return i;
			}
			i++;
		}
		return -1;
	};

	Array.prototype.indexIfa = function <T>(predicate: (value: T) => boolean): T {
		for (const a of this) {
			if (a != null && predicate(a)) return a;
		}
		return null;
	};

	Array.prototype.removeIf = function <T>(predicate: (value: T) => boolean): number {
		let i = this.length;
		let num = 0;
		while (i--) {
			if (this[i] != null && predicate(this[i])) {
				this.splice(i, 1);
				num++;
			}
		}
		return num;
	};

	Array.prototype.removeIfa = function <T>(predicate: (value: T) => boolean): T {
		let i = this.length;
		while (i--) {
			const a = this[i];
			if (a != null && predicate(a)) {
				this.splice(i, 1);
				return a;
			}
		}
		return null;
	};

	Array.prototype.removeIfrt = function <T>(predicate: (value: T) => boolean): Array<T> {
		let i = this.length;
		// let num = 0;
		const rt = [];
		while (i--) {
			const a = this[i];
			if (a != null && predicate(a)) {
				rt.push(a);
				this.splice(i, 1);
				// num++;
			}
		}
		return rt;
	};

	Array.prototype.count = function <T>(): number {
		let count = 0;
		let i = this.length;
		while (i--) {
			if (this[i] != null) count++;
		}
		return count;
	};

	Array.prototype.countIf = function <T>(predicate: (value: T) => boolean): number {
		let count = 0;
		let i = this.length;
		while (i--) {
			if (this[i] != null && predicate(this[i])) count++;
		}
		return count;
	};

	Array.prototype.shuffle = function <T>(): void {
		let currentIndex = this.length;
		let temporaryValue;
		let randomIndex;

		// While there remain elements to shuffle...
		while (currentIndex !== 0) {
			// Pick a remaining element...
			randomIndex = Math.floor(Math.random() * currentIndex);
			currentIndex -= 1;

			// And swap it with the current element.
			temporaryValue = this[currentIndex];
			this[currentIndex] = this[randomIndex];
			this[randomIndex] = temporaryValue;
		}
	};
}

// use a file to GdArray0

// export function countArrayNot0(a: any[]): number {
//     return a.reduce((total:number, item) => total + (item ? 1 : 0));
// }

// export class GdArray0<T> {
// 	ITEMS = []

// 	FREEINDEXS: number[] = []

// 	constructor() {
// 		this.ITEMS.push(<any>0); // 把0这个index占住不让人用！
// 	}

// 	findFree(): number {
// 		const id = this.FREEINDEXS.pop();
// 		if (id == undefined) return this.ITEMS.length;
// 		return id;
// 	}

// 	addItem(id: number, item: T) {
// 		this.ITEMS[id] = item;
// 	}

// 	newItem(item: T) {
// 		const id = this.findFree();
// 		this.addItem(id, item);
// 		return id;
// 	}

// 	delItem(id: number) {
// 		this.FREEINDEXS.push(id);
// 		delete this.ITEMS[id];
// 	}

// 	itemNum() {
// 		return countArrayNot0(this.ITEMS);
// 	}
// }
